﻿/****************************************************
	文件：Server.cs
	作者：HuskyT
	邮箱:  1005240602@qq.com
	日期：2020/04/23 23:09   	
	功能：服务器
*****************************************************/

using HTServer.Proto;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;

namespace HTServer.Net
{
    public class Server : Singleton<Server>
    {
        /// <summary>
        /// 公钥
        /// </summary>
        public static string PublicKey = "HuskyTGame.Github.IO";
        /// <summary>
        /// 密钥
        /// </summary>
        public static string SecretKey = "+94huskytLearningC#SimpleServer!";

#if DEBUG
        private static string mIpStr = "127.0.0.1";
#else
        //对应 阿里云/腾讯云 的本地IP地址（非公网IP地址）
        private static string mIpStr = "";
#endif
        private const int mPort = 8888;//端口号
        /// <summary>
        /// 监听客户端的Socket（服务器Socket）
        /// </summary>
        private static Socket mListennerSocket;
        /// <summary>
        /// 临时保存所有Socket的集合（多路复用Select）
        /// </summary>
        private static List<Socket> mCheckReadList = new List<Socket>();
        /// <summary>
        /// 缓存所有客户端的字典
        /// </summary>
        public static Dictionary<Socket, Client> mClientDict = new Dictionary<Socket, Client>();
        /// <summary>
        /// 临时的Client列表，用于删除超时连接（心跳包）
        /// </summary>
        private static List<Client> mTempClientList = new List<Client>();
        /// <summary>
        /// 心跳包发送时间间隔
        /// </summary>
        private static long mPingTimeInterval = 30;
        /// <summary>
        /// 心跳包超时比例（超过mPingTimeoutRatio个心跳包时间间隔就被视为连接超时）
        /// </summary>
        private static long mPingTimeoutRatio = 4;

        public void Init()
        {
            mListennerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAddress = IPAddress.Parse(mIpStr);
            //终端：IP地址 + 端口号
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, mPort);
            //Socket 绑定 IP地址/端口号
            mListennerSocket.Bind(ipEndPoint);
            //设定最大监听数量
            mListennerSocket.Listen(50000);
            Debug.LogInfo("服务器启动监听{0}成功", mListennerSocket.LocalEndPoint);

            while (true)
            {
                //检查是否有读取的Socket

                //找出所有Socket
                ResetCheckRead();

                try
                {
                    //Select（多路复用）
                    //最后等待时间：1000 微秒
                    Socket.Select(mCheckReadList, null, null, 1000);
                }
                catch (Exception e)
                {
                    Debug.LogError(e.ToString());
                }
                for (int i = mCheckReadList.Count - 1; i >= 0; i--)
                {
                    Socket socket = mCheckReadList[i];
                    //服务器接收到了客户端的连接请求（此时服务器Socket可读）
                    if (socket == mListennerSocket)
                    {
                        //读取Listenner（处理客户端连接）
                        ReadListenner(mListennerSocket);
                    }
                    //客户端发送过来了消息（此时客户端Socket可读）
                    else
                    {
                        //读取客户端（处理客户端发送来的消息）
                        ReadClient(socket);
                    }
                }

                //心跳包超时检测
                long nowTime = GetTimeStamp();
                mTempClientList.Clear();
                foreach (Client client in mClientDict.Values)
                {
                    if (nowTime - client.LastPingTime > mPingTimeInterval * mPingTimeoutRatio)//连接超时
                    {
                        mTempClientList.Add(client);//加入临时列表，便于客户端关闭（foreach的同时不能移除）
                    }
                }
                foreach (Client client in mTempClientList)
                {
                    Debug.Log("Ping close : {0}", client.Socket.RemoteEndPoint.ToString());
                    CloseClient(client);
                }


            }
        }

        /// <summary>
        /// 重置CheckReadList（找出所有Socket）
        /// </summary>
        private void ResetCheckRead()
        {
            mCheckReadList.Clear();
            mCheckReadList.Add(mListennerSocket);
            foreach (Socket socket in mClientDict.Keys)
            {
                mCheckReadList.Add(socket);
            }
        }
        /// <summary>
        /// 读取Listenner（处理客户端连接）
        /// </summary>
        private void ReadListenner(Socket listenner)
        {
            try
            {
                Socket clientSocket = listenner.Accept();
                Client client = new Client();
                client.Socket = clientSocket;
                client.LastPingTime = GetTimeStamp();
                mClientDict.Add(clientSocket, client);
                Debug.Log("客户端{0}连接到服务器，当前服务器在线人数：{1}", clientSocket.LocalEndPoint, mClientDict.Count);
            }
            catch (SocketException e)
            {
                Debug.LogError("Accept Client Failed : {0}", e.ToString());
            }
        }
        /// <summary>
        /// 读取客户端（处理客户端发送来的消息）
        /// </summary>
        private void ReadClient(Socket clientSocket)
        {
            Client client = mClientDict[clientSocket];
            ByteArray readBuff = client.ReadBuff;//客户端读取数据的缓冲
            //检测是否需要扩充ByteArray缓冲大小
            if (readBuff.Remain <= 0)//上一次接收的数据刚好沾满1024字节的数组
            {
                OnReveiveData(client);
                readBuff.CheckAndMoveBytes();//数据移动到 index=0 的位置
                while (readBuff.Remain <= 0)//保证数组容量大于接收到的数据的长度，以保证数据正常接收
                {
                    int expandSize = readBuff.Length < ByteArray.DEFAULT_SIZE ? ByteArray.DEFAULT_SIZE : readBuff.Length;
                    readBuff.ReSize(expandSize * 2);
                }
            }
            int count = 0;
            try
            {
                count = clientSocket.Receive(readBuff.Bytes, readBuff.WriteIndex, readBuff.Remain, 0);//接收数据（从WriteIndex开始写入）
            }
            catch (SocketException e)
            {
                Debug.LogError("clientSocket Receive Failed : {0}", e.ToString());
                CloseClient(client);
                return;
            }
            if (count <= 0)//接收到的数据长度小于等于0（客户端断开连接）
            {
                CloseClient(client);
                return;
            }
            readBuff.WriteIndex += count;//更新写入索引
            //处理接收到的数据包
            OnReveiveData(client);

            readBuff.CheckAndMoveBytes();
        }

        /// <summary>
        /// 处理接收到的数据包（处理分包粘包产生的问题）
        /// </summary>
        private void OnReveiveData(Client client)
        {
            ByteArray readBuff = client.ReadBuff;
            if (readBuff.Length < 4 || readBuff.ReadIndex < 0) return;
            int readIndex = readBuff.ReadIndex;//读取索引
            byte[] receiveBytes = readBuff.Bytes;//接收到的数据
            int msgLen = BitConverter.ToInt32(receiveBytes, readIndex);//消息长度
            //检测消息是否完整（是否有分包）：
            if (readBuff.Length < msgLen + 4) return;//消息不全则等待下一次继续读取
            readBuff.ReadIndex += 4;//移动读取索引
            //解析协议名：
            int nameLen = 0;
            ProtoEnum protoType = ProtoEnum.None;
            try
            {
                protoType = MsgTools.DecodeName(readBuff.Bytes, readBuff.ReadIndex, out nameLen);
            }
            catch (Exception e)
            {
                Debug.LogError("接收信息时，解析协议名出错：{0}", e.ToString());
                CloseClient(client);
                return;
            }
            if (protoType == ProtoEnum.None)
            {
                Debug.LogError("接收信息时，解析协议名出错！");
                CloseClient(client);
                return;
            }
            readBuff.ReadIndex += nameLen;//移动读取索引
            //解析消息体：
            int bodyLen = msgLen - nameLen;
            MsgBase msg = null;
            try
            {
                msg = MsgTools.Decode(protoType, readBuff.Bytes, readBuff.ReadIndex, bodyLen);
                if (msg == null)
                {
                    Debug.LogError("接收信息时，解析{0}协议的消息体出错！", protoType.ToString());
                    CloseClient(client);
                    return;
                }
            }
            catch (Exception e)
            {
                Debug.LogError("接收信息时，解析消息体出错：{0}", e.ToString());
                CloseClient(client);
                return;
            }
            readBuff.ReadIndex += bodyLen;//移动读取索引
            readBuff.CheckAndMoveBytes();//检测、移动
            //分发消息（使用反射 调用 具体的处理逻辑）：
            MethodInfo methodInfo = typeof(MsgHandler).GetMethod(protoType.ToString());
            object[] paramArray = { client, msg };
            if (methodInfo != null)
            {
                methodInfo.Invoke(null, paramArray);
            }
            else
            {
                Debug.LogError("{0}协议分发消息失败！", protoType.ToString());
            }
            //继续读取消息（处理粘包问题）：
            if (readBuff.Length > 4)
            {
                OnReveiveData(client);
            }
        }

        /// <summary>
        /// 发送消息（数据包）
        /// 在MsgHandler中调用
        /// </summary>
        public static void Send(Client client, MsgBase msg)
        {
            if (client == null || client.Socket.Connected == false) return;
            try
            {
                //包装消息：
                byte[] nameBytes = MsgTools.EncodeName(msg);//消息名
                byte[] bodyBytes = MsgTools.Encode(msg);//消息主体
                //消息长度（使用 int32 ，即4个字节长度）
                int msgLen = nameBytes.Length + bodyBytes.Length;
                byte[] headBytes = BitConverter.GetBytes(msgLen);//消息头（存储消息长度，占用4字节）
                byte[] sendBytes = new byte[headBytes.Length + nameBytes.Length + bodyBytes.Length];
                Array.Copy(headBytes, 0, sendBytes, 0, headBytes.Length);
                Array.Copy(nameBytes, 0, sendBytes, headBytes.Length, nameBytes.Length);
                Array.Copy(bodyBytes, 0, sendBytes, headBytes.Length + nameBytes.Length, bodyBytes.Length);
                //发送消息：
                try
                {
                    client.Socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, null, null);//开始发送消息
                }
                catch (SocketException se)
                {
                    Debug.LogError("Socket BeginSend Error：{0}", se.ToString());
                }
            }
            catch (SocketException e)
            {
                Debug.LogError("发送给客户端消息失败：{0}", e.ToString());
            }
        }

        /// <summary>
        /// 关闭客户端
        /// </summary>
        private void CloseClient(Client client)
        {
            client.Socket.Close();
            mClientDict.Remove(client.Socket);
            Debug.Log("一个客户端断开连接，当前服务器在线人数：{0}", mClientDict.Count);
        }

        #region Helper Function
        /// <summary>
        /// 获取当前的时间戳
        /// </summary>
        public static long GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds);
        }
        /// <summary>
        /// 获取当前时间（xx时xx分xx秒）
        /// </summary>
        public static DateTime GetNowTime()
        {
            return DateTime.Now;
        }
        #endregion
    }
}